// <copyright file="ResizeInsertTests.cs" company="Sinbadsoft">
//        Copyright (c) Sinbadsoft 2009.
//        This file is released under the terms of the
//        GNU Library General Public License (LGPL) version 2.1
//        Please refer to the "License.txt" accompanying this file.
// </copyright>
// <author>Chaker NAKHLI</author>
// <email>Chaker.Nakhli@Sinbadsoft.com</email>
// <date>2009/03/08</date>

namespace Sinbadsoft.AdvancedCollections.Tests.DynamicArray
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Globalization;
    using MbUnit.Framework;

    [TestFixture]
    public static class ResizeInsertTests
    {
        private static readonly int[] zero2DIdx = { 0, 0 };

        [StaticTestFactory]
        public static IEnumerable<Test> BuildTests()
        {
            IList<Test> tests = new List<Test>();
            InsertionData[] data = 
                                {
                                    new InsertionData(100, 1, 0), new InsertionData(17, 2, 0),
                                    new InsertionData(17, 2, 1), new InsertionData(16, 3, 0),
                                    new InsertionData(16, 3, 1), new InsertionData(16, 3, 2),
                                    new InsertionData(7, 4, 0), new InsertionData(8, 4, 2), 
                                    new InsertionData(8, 4, 3), new InsertionData(5, 5, 0), 
                                    new InsertionData(2, 10, 8), new InsertionData(1, 16, 14)
                                 };
            for (int i = 0; i < data.Length; i++)
            {
                InsertionData idata = data[i];
                tests.Add(new TestCase(idata.ToString(), delegate { InsertTest(idata); }));
            }

            return tests;
        }

        [Test]
        public static void ExceptionOnInsertArrayOfInvalidRank()
        {
            DynamicArray<int> dynArray = new DynamicArray<int>(2);
            Assert.Throws<RankException>(
                delegate
                {
                    dynArray.Insert(new int[5], 0, 0, 0);
                });
            Assert.Throws<RankException>(
                delegate
                {
                    dynArray.Insert(new int[5, 10, 12], 0, 0, 0);
                });
        }

        [Test]
        public static void ExceptionOnInsertArrayWithANullLength()
        {
            DynamicArray<int> dynArray = new DynamicArray<int>(2);
            Assert.Throws<ArgumentException>(
                delegate
                {
                    dynArray.Insert(new int[5, 0], 0, 0, 0);
                });
        }

        [Test]
        public static void ResizeWithSameSize()
        {
            DynamicArray<int> darray = new DynamicArray<int>(1);
            
            IEnumerator invalidated = darray.GetEnumerator();
            Assert.AreEqual(0, darray.Count);
            darray.Resize(100);
            Assert.AreEqual(100, darray.Count);
            Assert.Throws<InvalidOperationException>(delegate { invalidated.MoveNext(); });
            
            IEnumerator notInvalidated = darray.GetEnumerator();
            Assert.AreEqual(100, darray.Count);
            darray.Resize(100);
            Assert.AreEqual(100, darray.Count);
            Assert.DoesNotThrow(delegate { notInvalidated.MoveNext(); });
        }

        [Test]
        public static void Insert2D()
        {
            const int O_ROWS = 4;
            const int O_COLS = 2;
            const int A_ROWS = 4;
            const int A_COLS = 3;
            const int B_ROWS = 2;
            const int B_COLS = 2;

            // Arrays O, A and B
            object[,] oarray = ArrayHelper.New().NewArray<object>(O_ROWS, O_COLS).FillWith("O").As<object[,]>();
            object[,] aarray = ArrayHelper.New().NewArray<object>(A_ROWS, A_COLS).FillWith("A").As<object[,]>();
            object[,] barray = ArrayHelper.New().NewArray<object>(B_ROWS, B_COLS).FillWith("B").As<object[,]>();

            // NOTE capacities are set to the minimum to force buffer resize 
            DynamicArray<object> dynArray = new DynamicArray<object>(2, 1, 1);

            // Insert O at (O_ROW_POS, O_COL_POS)
            const int O_ROW_POS = 0;
            const int O_COL_POS = 2;
            dynArray.Insert(oarray, 0, O_ROW_POS, O_COL_POS);
            ArrayHelper.Print(dynArray);
            DynamicArrayAssert.Included(dynArray, oarray, O_ROW_POS, O_COL_POS);
            DynamicArrayAssert.CountsEqual(dynArray, O_ROW_POS + O_ROWS, O_COL_POS + O_COLS);

            // Insert A at (A_ROW_POS, A_COL_POS) in dim 0
            // Must have: (A_ROW_POS > O_ROW_POS) && (A_ROW_POS < O_ROW_POS + O_ROWS) 
            // in order to insert A "within" O
            const int A_ROW_POS = 1;
            const int A_COL_POS = 1;
            Assert.IsTrue(A_ROW_POS > O_ROW_POS);
            Assert.IsTrue(A_ROW_POS < O_ROW_POS + O_ROWS);
            dynArray.Insert(aarray, 0, A_ROW_POS, A_COL_POS);
            ArrayHelper.Print(dynArray);
            DynamicArrayAssert.Included(dynArray, aarray, A_ROW_POS, A_COL_POS);
            DynamicArrayAssert.Included(
                    dynArray,
                    oarray,
                    new int[] { O_ROW_POS, O_COL_POS },
                    zero2DIdx,
                    new int[] { A_ROW_POS - O_ROW_POS, O_COLS });
            DynamicArrayAssert.Included(
                    dynArray,
                    oarray,
                    new int[] { A_ROW_POS + A_ROWS, O_COL_POS },
                    new int[] { A_ROW_POS - O_ROW_POS, 0 },
                    new int[] { O_ROWS, O_COLS });

            // Insert B at (B_ROW_POS, B_COL_POS) in dim 1
            const int B_ROW_POS = 2;
            const int B_COL_POS = 1;
            dynArray.Insert(barray, 1, B_ROW_POS, B_COL_POS);
            ArrayHelper.Print(dynArray);
            DynamicArrayAssert.Included(dynArray, barray, B_ROW_POS, B_COL_POS);
            DynamicArrayAssert.Included(
                    dynArray,
                    aarray,
                    new int[] { B_ROW_POS, B_COL_POS + B_COLS },
                    new int[] { B_ROW_POS - A_ROW_POS, B_COL_POS - A_COL_POS },
                    new int[] { B_ROW_POS - A_ROW_POS + B_ROWS, A_COLS });
        }

        [Test]
        public static void Resize2D()
        {
            // Should have B_COLS < A_COLS
            const int A_ROWS = 4;
            const int A_COLS = 3;
            const int B_ROWS = 5;
            const int B_COLS = 2;

            // Arrays O, A and B
            object[,] arrA = ArrayHelper.New().NewArray<object>(A_ROWS, A_COLS).FillWith("A").As<object[,]>();
            object[,] arrB = ArrayHelper.New().NewArray<object>(B_ROWS, B_COLS).FillWith("B").As<object[,]>();

            // NOTE capacities are set to the minimum to force buffer resize 
            DynamicArray<object> dynArray = new DynamicArray<object>(2, 1, 1);

            // Insert B at 0,0
            dynArray.Insert(arrB, 0, 0, 0);
            ArrayHelper.Print(dynArray);
            DynamicArrayAssert.AreElementsEqual(dynArray, arrB);

            // Insert A right before B in dim 1
            dynArray.Insert(arrA, 0, 0, 0);
            ArrayHelper.Print(dynArray);
            DynamicArrayAssert.Included(dynArray, arrA, 0, 0);
            DynamicArrayAssert.Included(dynArray, arrB, A_ROWS, 0);
            DynamicArrayAssert.CountsEqual(dynArray, A_ROWS + B_ROWS, A_COLS);

            // Remove B_ROWS lines and add NCols columns
            const int NCols = 5;
            dynArray.Resize(A_ROWS, A_COLS + NCols);
            ArrayHelper.Print(dynArray);
            DynamicArrayAssert.Included(dynArray, arrA, 0, 0);
            DynamicArrayAssert.AreElementsDefault(dynArray, new int[] { A_ROWS, NCols }, 0, A_COLS);
            DynamicArrayAssert.CountsEqual(dynArray, A_ROWS, A_COLS + NCols);

            // Add NRows rows
            const int NRows = 10;
            dynArray.ResizeDim(0, A_ROWS + NRows);
            ArrayHelper.Print(dynArray);
            DynamicArrayAssert.CountsEqual(dynArray, A_ROWS + NRows, A_COLS + NCols);
            DynamicArrayAssert.Included(dynArray, arrA, 0, 0);
            DynamicArrayAssert.AreElementsDefault(dynArray, new int[] { NRows, A_COLS + NCols }, A_ROWS, 0);
            DynamicArrayAssert.AreElementsDefault(dynArray, new int[] { A_ROWS + NRows, NCols }, 0, A_COLS);

            // Shrink to (NFewRows, NFewCols)
            const int NFewCols = 2;
            const int NFewRows = 3;
            dynArray.Resize(NFewRows, NFewCols);
            ArrayHelper.Print(dynArray);
            DynamicArrayAssert.Included(dynArray, arrA, zero2DIdx, zero2DIdx, new int[] { NFewRows, NFewCols });

            // Enlarge to (NManyRows, NManyCols) 
            const int NManyRows = 10;
            const int NManyCols = 7;
            dynArray.Resize(NManyRows, NManyCols);
            ArrayHelper.Print(dynArray);
            DynamicArrayAssert.Included(dynArray, arrA, zero2DIdx, zero2DIdx, new int[] { NFewRows, NFewCols });
            DynamicArrayAssert.AreElementsDefault(dynArray, new int[] { NManyRows, NManyCols - NFewCols }, 0, NFewCols);
            DynamicArrayAssert.AreElementsDefault(dynArray, new int[] { NManyRows - NFewRows, NFewCols }, NFewRows, 0);
        }

        private static void InsertTest(InsertionData idata)
        {
            int[] zeroIdx = IndexesHelper.Zero(idata.Rank);

            // Build O and A hypercubes lengths
            int[] alengths = IndexesHelper.Add(zeroIdx, idata.Length);
            int[] olengths = IndexesHelper.Multiply(alengths, 2);

            // Build O and A hypercubes
            Array oarray = ArrayHelper.New().NewArray<object>(olengths).FillWith("O").As<Array>();
            Array aarray = ArrayHelper.New().NewArray<object>(alengths).FillWith("A").As<Array>();

            // NOTE capacities are set to the minimum to force buffer resize
            int[] capacities = IndexesHelper.Add(IndexesHelper.Zero(idata.Rank), 1);
            DynamicArray<string> dynarray = new DynamicArray<string>(idata.Rank, capacities);

            dynarray.Insert(oarray, 0, zeroIdx);
            DynamicArrayAssert.Included(dynarray, oarray, IndexesHelper.Zero(idata.Rank));
            DynamicArrayAssert.CountsEqual(dynarray, olengths);

            int[] pos = IndexesHelper.Add(alengths, -1);
            int[] expected = IndexesHelper.Clone(olengths);
            expected[idata.Dim] += alengths[idata.Dim];
            dynarray.Insert(aarray, idata.Dim, pos);
            DynamicArrayAssert.CountsEqual(dynarray, expected);
            DynamicArrayAssert.Included(dynarray, aarray, pos);
            DynamicArrayAssert.Included(
                dynarray, 
                oarray,
                IndexesHelper.Add(pos, alengths),
                idata.Rank > 1 ? IndexesHelper.Add(pos, alengths) : pos, 
                olengths);
        }

        private struct InsertionData
        {
            /// <summary> Hypercube length. </summary>
            public readonly int Length;

            /// <summary> Hypercube rank. </summary>
            public readonly int Rank;

            /// <summary> Dimension where to insert. </summary>
            public readonly int Dim;

            public InsertionData(int length, int rank, int dim)
            {
                this.Length = length;
                this.Rank = rank;
                this.Dim = dim;
            }

            public override string ToString()
            {
                return string.Format(CultureInfo.InvariantCulture, "Rank ({0}) Length:{1} Dim:{2}", this.Rank, this.Length, this.Dim);
            }
        }
    }
}
